package com.dycong.common.leetcode;

/**
 * * 打家劫舍
 * * 你是一个专业的小偷，计划偷窃沿街的房屋。每间房内都藏有一定的现金，影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统，如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警。
 * *
 * * 给定一个代表每个房屋存放金额的非负整数数组，计算你在不触动警报装置的情况下，能够偷窃到的最高金额。
 * *
 * * 示例 1:
 * *
 * * 输入: [1,2,3,1]
 * * 输出: 4
 * * 解释: 偷窃 1 号房屋 (金额 = 1) ，然后偷窃 3 号房屋 (金额 = 3)。
 * *      偷窃到的最高金额 = 1 + 3 = 4 。
 * * 示例 2:
 * *
 * * 输入: [2,7,9,3,1]
 * * 输出: 12
 * * 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9)，接着偷窃 5 号房屋 (金额 = 1)。
 * *      偷窃到的最高金额 = 2 + 9 + 1 = 12 。
 *
 * @author dycong
 * @date 2019/12/4 12:19
 */
public class Rob_1_198 {
    public static void main(String[] args) {
        Rob_1_198 rob_1_198 = new Rob_1_198();

        System.out.println(rob_1_198.rob(new int[]{2, 7, 9, 3, 1}));
        System.out.println(rob_1_198.rob_better(new int[]{2, 7, 9, 3, 1}));
    }

    /**
     * 标签：动态规划
     * 动态规划方程：dp[n] = MAX( dp[n-1], dp[n-2] + num )
     * 由于不可以在相邻的房屋闯入，所以在当前位置 n 房屋可盗窃的最大值，要么就是 n-1 房屋可盗窃的最大值，要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值，二者之间取最大值
     * 举例来说：1 号房间可盗窃最大值为 33 即为 dp[1]=3，2 号房间可盗窃最大值为 44 即为 dp[2]=4，3 号房间自身的值为 22 即为 num=2，那么 dp[3] = MAX( dp[2], dp[1] + num ) = MAX(4, 3+2) = 5，3 号房间可盗窃最大值为 55
     * 时间复杂度：O(n)O(n)，nn 为数组长度
     * <p>
     * 作者：guanpengchn
     * 链接：https://leetcode-cn.com/problems/house-robber/solution/hua-jie-suan-fa-198-da-jia-jie-she-by-guanpengchn/
     * f(k) = max(f(k-2)+k,f(k-1))
     *
     * @param nums
     * @return
     */
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;

        if (nums.length == 1) return nums[0];

        int[] memo = new int[nums.length + 1];
        memo[0] = 0;
        memo[1] = nums[0];
        for (int i = 2; i < nums.length + 1; i++) {
            memo[i] = Math.max(memo[i - 2] + nums[i - 1], memo[i - 1]);
        }

        return memo[nums.length];
    }

    /**
     * 动态规划
     * 算法
     * <p>
     * 考虑所有可能的抢劫方案过于困难。一个自然而然的想法是首先从最简单的情况开始。记：
     * <p>
     * f(k) = 从前 k 个房屋中能抢劫到的最大数额，A_iA
     * i
     * ​
     * = 第 i 个房屋的钱数。
     * <p>
     * 首先看 n = 1 的情况，显然 f(1) = A_1A
     * 1
     * ​
     * 。
     * <p>
     * 再看 n = 2，f(2) = max(A_1A
     * 1
     * ​
     * , A_2A
     * 2
     * ​
     * )。
     * <p>
     * 对于 n = 3，有两个选项:
     * <p>
     * 抢第三个房子，将数额与第一个房子相加。
     * <p>
     * 不抢第三个房子，保持现有最大数额。
     * <p>
     * 显然，你想选择数额更大的选项。于是，可以总结出公式：
     * <p>
     * f(k) = max(f(k – 2) + A_kA
     * k
     * ​
     * , f(k – 1))
     * <p>
     * 我们选择 f(–1) = f(0) = 0 为初始情况，这将极大地简化代码。
     * <p>
     * 答案为 f(n)。可以用一个数组来存储并计算结果。不过由于每一步你只需要前两个最大值，两个变量就足够用了。
     *
     * @param nums
     * @return
     */
    public int rob_better(int[] nums) {
        if (nums.length == 0) return 0;

        if (nums.length == 1) return nums[0];

        int first = 0, second = 0;

        for (int num : nums) {
            int tmp = first;
            first = second;
            second = Math.max(num + tmp, first);
        }
        return Math.max(first, second);
    }

}
